#!/bin/env python

import time
import sys
import os
import shutil
import subprocess
import re
import zipfile
from fnmatch import fnmatch
from distutils import dir_util, log
from distutils.core import setup, Command
from distutils.command.bdist import bdist as _bdist_orig
from distutils.command.clean import clean as _clean_orig
from distutils.util import get_platform
from os.path import isdir, exists, join, walk, splitext

copyright = 'Copyright (C) 2007-2015'


def runcmd(cmds, cwd=None, env=None):
    if env is None:
        env = os.environ
    if cwd is None:
        cwd = os.getcwd()
    if os.name != 'nt':
        import shlex
        cmds = shlex.split(cmds)

    p = subprocess.Popen(
        cmds, cwd=cwd, env=env,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE)

    out, err = p.communicate()
    if p.returncode != 0:
        return ''

    return out


def candle(file, sourcedir=None, cwd='.', defines=None):
    args = []
    if sourcedir:
        args.append('-dSourceDir="%s"' % sourcedir)
    if defines:
        args += ['-d%s="%s"' % item for item in defines]
    runcmd(r'candle -nologo "%s" %s' % (file, ' '.join(args)), cwd)


def light(output, cwd, wixdir, *files):
    runcmd(r'light -nologo -ext WixUIExtension -sw1076 -spdb -o %s %s' %
           (output, ' '.join(join(wixdir, f.replace('.wxs', '.wixobj'))
                             for f in files)), cwd)


def spinx(format, cwd='.', output='build'):
    args = {}
    args['cwd'] = cwd
    args['format'] = format
    args['output'] = output

    runcmd(r'sphinx-build -b %(format)s -d %(output)s/doctrees '
           r'%(cwd)s %(output)s/%(format)s' % args)


def zipfiles(file, *dirs):
    with zipfile.ZipFile(file, 'w') as f:
        for path in dirs:
            for root, _, files in os.walk(path):
                for fname in files:
                    name = join(root, fname)
                    f.write(name, name[len(path) + 1:], zipfile.ZIP_DEFLATED)


class build_doc(Command):
    description = "build document (.rst files)"
    user_options = []

    def initialize_options(self):
        self.doc_dir = None
        self.build_dir = None

    def finalize_options(self):
        if self.doc_dir is None:
            self.doc_dir = 'doc'
        if self.build_dir is None:
            self.build_dir = 'build'

    def run(self):
        chm_dir = join(self.build_dir, 'chm')
        htb_dir = join(self.build_dir, 'htb')
        htmlhelp_dir = join(self.build_dir, 'htmlhelp')

        if not exists(self.doc_dir):
            log.error('"doc" directory is not existent' % self.doc_dir)
            return

        spinx('htmlhelp', self.doc_dir, self.build_dir)

        def _mkdir(path):
            if not exists(path):
                os.makedirs(path)

        # build chm for Windows
        if os.name == 'nt':
            chm_file = join(chm_dir, 'aof.chm')
            if exists(chm_file):
                os.remove(chm_file)

            _mkdir(chm_dir)
            runcmd('hhc %s' % join(htmlhelp_dir, 'aof.hhp'))
            shutil.move(
                join(htmlhelp_dir, 'aof.chm'), chm_file)

        # create htb with patch.py
        _mkdir(htb_dir)
        patch = join(self.doc_dir, 'patch.py')
        if exists(patch):
            runcmd('python %s %s' % (patch, htmlhelp_dir))

        htb_file = join(htb_dir, 'aof.htb')
        zipfiles(htb_file, htmlhelp_dir)
        # copy the htb to the root
        self.copy_file(htb_file, '.')


class build_zip(Command):
    description = "build windows executable zipfile"
    user_options = []

    def initialize_options(self):
        self.keep_temp = 0
        self.dist_dir = None
        self.build_dir = None

    def finalize_options(self):
        if self.dist_dir is None:
            self.dist_dir = "dist"
        if self.build_dir is None:
            self.build_dir = "build"

    def run(self):
        base_dir = self.distribution.get_fullname()
        if not exists(self.dist_dir):
            log.error('%s is not existed, use "setup.py py2exe" to build first'
                      % self.dist_dir)
        elif not exists(base_dir):
            log.error('%s is not existed, use "setup.py sdist" to build first'
                      % base_dir)
        elif not exists(self.build_dir):
            log.error('%s is not existed, use "setup.py bdist" to build first'
                      % self.build_dir)
        else:
            source_dir = '%s_exe' % base_dir
            if os.path.exists(source_dir):
                dir_util.remove_tree(source_dir, dry_run=self.dry_run)
            os.makedirs(source_dir)
            # copy files from the 'dist'
            for f in os.listdir(self.dist_dir):
                fn = join(self.dist_dir, f)
                if fn.endswith('library.zip') or (
                        not isdir(fn)
                        and not fn.endswith('.msi')
                        and not fn.endswith('.zip')):
                    self.copy_file(fn, source_dir)
            chm = join(self.build_dir, 'chm', 'aof.chm')
            if exists(chm):
                self.copy_file(chm, source_dir)
            else:
                # copy the htb file
                htb = join(self.build_dir, 'htb', 'aof.htb')
                if exists(htb):
                    self.copy_file(htb, source_dir)
            # copy the plugins
            if exists(base_dir):
                plxtree = join(base_dir, 'plugins')
                self.copy_tree(plxtree, join(source_dir, 'plugins'))

            self.make_archive(join(self.dist_dir, source_dir),
                              'zip', base_dir=source_dir)

            if not self.keep_temp:
                dir_util.remove_tree(source_dir, dry_run=self.dry_run)

        # remove sdist temp
        dir_util.remove_tree(base_dir, dry_run=self.dry_run)


class build_msi(Command):
    description = "build msi installer"
    user_options = []

    def initialize_options(self):
        self.win_dir = None
        self.dist_dir = None

    def finalize_options(self):
        if self.win_dir is None:
            self.win_dir = 'win32'
        if self.dist_dir is None:
            self.dist_dir = 'dist'

    def run(self):
        version_ = self.distribution.get_version()
        if version_.find('+') > 0:
            version_ = version_[:version_.find('+')]

        platform_ = sys.version.find('64 bit') > -1 and 'x64' or 'x86'
        args = (
            ('Platform', platform_),
            ('Version', version_),
            ('Comments', 'msi')
        )

        wix_dir = join(self.win_dir, 'wix')
        wix_files = {
            'dist.wxs': 'dist',
            'plugins.wxs': 'plugins',
            'samples.wxs': 'test/sample',
            'anafile.wxs': '.'
        }

        for wix in wix_files:
            candle(wix, join(os.getcwd(), wix_files[wix]), wix_dir, args)

        light(join(self.dist_dir,
                   '%s-%s_%s.msi' % (
                       self.distribution.get_name(),
                       self.distribution.get_version(),
                       get_platform())),
              '.', wix_dir, *wix_files)


class clean_local(Command):
    pats = ('*.py[co]', '*.mo', '*.orig', '*.rej', '*.msi', '*.wixobj',
            '*aof', '*aofw', '*MANIFEST', '*build*.html',
            '*anafile/__version__.py')
    excludedirs = ['.hg', 'dist']
    description = 'clean up generated files (%s)' % ', '.join(pats)
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        for e in self._walkpaths('.'):
            log.info("removing '%s'" % e)
            os.remove(e)

    def _walkpaths(self, path):
        for root, _, files in os.walk(path):
            if any(root == join(path, e) or root.startswith(join(path, e, ''))
                   for e in self.excludedirs):
                continue
            for e in files:
                fpath = join(root, e)
                if any(fnmatch(fpath, p) for p in self.pats):
                    yield fpath


class bdist(_bdist_orig):
    def _is_nt(self):
        return os.name == 'nt'

    def run(self):
        # _bdist_orig.run(self)
        for e in self.get_sub_commands():
            self.run_command(e)

    sub_commands = [
        ('build_doc', None),
        ('build_zip', _is_nt),
        ('build_msi', _is_nt),
    ]


class clean(_clean_orig):
    sub_commands = [
        ('clean_local', None),
    ] + _clean_orig.sub_commands

    def run(self):
        _clean_orig.run(self)
        for e in self.get_sub_commands():
            self.run_command(e)


cmdclass = {
    'bdist': bdist,
    'build_doc': build_doc,
    'build_msi': build_msi,
    'build_zip': build_zip,
    'clean': clean,
    'clean_local': clean_local
}


def setup_windows(version):
    # Specific definitios for Windows NT-alike installations
    _scripts = []
    _data_files = []
    _packages = []
    extra = {}

    # py2exe needs to be installed to work
    try:
        import py2exe

        # Help py2exe to find win32com.shell
        try:
            import modulefinder
            import win32com
            for p in win32com.__path__[1:]:
                modulefinder.AddPackagePath("win32com", p)
            pn = "win32com.shell"
            __import__(pn)
            m = sys.modules[pn]
            for p in m.__path__[1:]:
                modulefinder.AddPackagePath(pn, p)
        except ImportError:
            pass

    except ImportError:
        if '--version' not in sys.argv:
            raise

    extra['options'] = {
        "py2exe": {
            "skip_archive": 0,
            "excludes": "pywin,pywin.dialogs,pywin.dialogs.list"
                        ",setup,distutils",
            "optimize": 1,
            "dll_excludes": ["MSVCP90.dll"]
        },
        "sdist": {
            "keep_temp": True
        }
    }

    extra['console'] = [
        {'script': 'aof',
         'icon_resources': [(0, 'icons/logo.ico')],
         'description': 'Console with Analyzer for Files',
         'copyright': copyright,
         'product_version': version},
    ]
    extra['windows'] = [
        {'script': 'aofw',
         'icon_resources': [(0, 'icons/logo.ico')],
         'description': 'Workbench with Analyzer for Files',
         'copyright': copyright,
         'product_version':version},
    ]

    return _scripts, _packages, _data_files, extra


def setup_posix(version):
    # TODO: Specific definitios for Posix installations
    extra = {}
    _data_files = []
    _scripts = []
    _packages = []

    return _scripts, _packages, _data_files, extra


def repoversion(verfile):
    vals = {}
    execfile(verfile, vals)
    return vals.get('version', 'unknown')


def hgversion():
    # get latest tag
    ret = ''
    version = 0

    result = runcmd('hg tags')
    for li in result.split('\n'):
        m = re.match(r'^(\S+)\s+(\d+):([a-z0-9]+)$', li)
        if m:
            if int(m.group(2)) > version and m.group(1) != 'tip':
                ret = m.group(1)
                version = int(m.group(2))

    result = runcmd('hg summary')
    m = re.match(r'^parent:\s+(\d+):([a-z0-9]+)\s+(\S+)', result)
    if m:
        if ret != m.group(3):
            ret += '+%s' % m.group(2)

    return ret


if __name__ == '__main__':

    version = ''
    author = 'CaDaPPL'
    if isdir('.hg'):
        version = hgversion()

        if version:
            f = open('anafile/__version__.py', 'w')
            f.write('# this file is autogenerated by setup.py\n')
            f.write('author = "%s"\n' % author)
            f.write('version = "%s"\n' % version)
            f.close()
    elif exists('anafile/__version__.py'):
        version = repoversion('anafile/__version__.py')

    setupversion = version
    if os.name == "nt":
        productname = 'AnaFile'
        (scripts, packages, data_files, extra) = setup_windows(version)
    else:
        productname = 'anafile'
        (scripts, packages, data_files, extra) = setup_posix(version)

    shutil.copyfile('anafile.py', 'aof')
    shutil.copyfile('anafile.py', 'aofw')

    setup(name=productname,
          version=setupversion,
          author=author,
          author_email='cadappl@gmail.com',
          url='https://code.google.com/p/analyzer-for-files',
          description='Analyzer for Files',
          license='GNU GPLv2',
          platforms=('Windows', 'Linux', 'OSX'),
          scripts=scripts,
          packages=packages,
          data_files=data_files,
          package_dir={'anafile': 'anafile'},
          cmdclass=cmdclass,
          **extra)

    for file_ in ('aof', 'aofw', 'aof.htb'):
        if exists(file_):
            os.unlink(file_)
